// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
 * Author: Huawei OS Kernel Lab
 * Create: Fri Sep 23 10:45:12 2022
 */

#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/namei.h>
#include <linux/file.h>
#include <linux/module.h>
#include <linux/uaccess.h>

enum LDK_FCMD {
	LDK_FCMD_MIN = 0,
	LDK_MKDIR,
	LDK_FCREATE,
	LDK_FREAD,
	LDK_FCMD_MAX,
};

#define MAX_FPATH	1024
static unsigned int cur_fcmd = LDK_FCMD_MIN;
static unsigned char cur_fpath[MAX_FPATH];

static int ldk_mkdir(const char *name, umode_t mode)
{
	struct dentry *dentry;
	struct path path;
	int err;

	dentry = kern_path_create(AT_FDCWD, name, &path, LOOKUP_DIRECTORY);
	if (IS_ERR(dentry))
		return PTR_ERR(dentry);

	err = vfs_mkdir(d_inode(path.dentry), dentry, mode);
	done_path_create(&path, dentry);
	return err;
}

static long ldk_files_ioctl(struct file *fdev, unsigned int cmd, unsigned long args)
{
	long ret = 0;

	switch (cmd) {
	case LDK_MKDIR:
		if (copy_from_user(cur_fpath, (void *)args, MAX_FPATH - 1))
			ret = -EFAULT;

		cur_fcmd = cmd;
		ret = ldk_mkdir(cur_fpath, 0755);
		break;
	case LDK_FCREATE:
		if (copy_from_user(cur_fpath, (void *)args, MAX_FPATH - 1))
			ret = -EFAULT;

		cur_fcmd = cmd;
		break;
	case LDK_FREAD:
		if (copy_from_user(cur_fpath, (void *)args, MAX_FPATH - 1))
			ret = -EFAULT;

		cur_fcmd = cmd;
		break;
	default:
		cur_fcmd = LDK_FCMD_MIN;
		ret = -EINVAL;
		break;
	}
	return ret;
}

static ssize_t ldk_files_read(struct file *fdev, char __user *buf, size_t size, loff_t *pos)
{
	struct file *file = NULL;
	int ret;

	if (cur_fcmd != LDK_FREAD)
		return -EINVAL;

	file = filp_open(cur_fpath, O_RDWR, 0755);

	if (IS_ERR(file)) {
		pr_err("filp_open(%s) for write failed\n", cur_fpath);
		return -ENOENT;
	}

	ret = vfs_read(file, buf, size, pos);

	fput(file);
	return ret;
}

static ssize_t ldk_files_write(struct file *fdev, const char __user *buf, size_t size, loff_t *pos)
{
	struct file *file = NULL;
	int ret;
	int flags = O_RDWR | O_CREAT;

	if (cur_fcmd != LDK_FCREATE)
		return -EINVAL;

	if (*pos == 0)
		flags |= O_TRUNC;

	file = filp_open(cur_fpath, flags, 0755);

	if (IS_ERR(file)) {
		pr_err("filp_open(%s) for write failed\n", cur_fpath);
		return -ENOENT;
	}

	ret = vfs_write(file, buf, size, pos);

	fput(file);
	return ret;
}

static int open_files_dev(struct inode *inode, struct file *filp)
{
	if (!liblinux_pal_capable(CAP_SYS_ADMIN)) {
		pr_err("no permission\n");
		return -EPERM;
	}
	return 0;
}

static const struct file_operations ldk_files_fops = {
	.owner		= THIS_MODULE,
	.unlocked_ioctl	= ldk_files_ioctl,
	.read		= ldk_files_read,
	.write		= ldk_files_write,
	.llseek		= default_llseek,
	.open		= open_files_dev,
};

static struct miscdevice ldk_files_dev = {
	.minor	= MISC_DYNAMIC_MINOR,
	.name	= "ldk_files",
	.fops	= &ldk_files_fops
};

static int __init ldk_files_ops_init(void)
{
	return misc_register(&ldk_files_dev);
}

static void __exit ldk_files_ops_deinit(void)
{
	misc_deregister(&ldk_files_dev);
}
module_init(ldk_files_ops_init);
module_exit(ldk_files_ops_deinit);
MODULE_LICENSE("GPL");
